/*
* Copyright (c) 2012 Kyle Hayward
* 
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
* 
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
* 
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#pragma once

#include <cuda.h>
#include <cuda_runtime.h>

#pragma comment( lib, "cudart.lib" )

using namespace System;

namespace OptixDotNet  {
namespace Utils
{
	/// <summary>
	/// Enum used to represent the status of a TryDetectGPUCompatibility.
	/// </summary>
	public enum class DetectGPUResult
	{
		/// <summary>
		/// Valid Nvidia GPU
		/// </summary>
		GpuValid,

		/// <summary>
		/// Invalid Nvidia GPU
		/// </summary>
		GpuNonValid,

		/// <summary>
		/// One or more Nvidia Dlls failed to load. This either means the user does not have an Nvidia Gpu, 
		/// or requisite dlls that should load weren't found (cudart.dll, optix.dll )
		/// </summary>
		FailedToLoadNvidiaDLL,

		/// <summary>
		/// A driver that is too old to run Optix/Cuda in installed
		/// </summary>
		InsufficientDriver
	};

	/// <summary>
	/// Static Utils class to aide in utility type functions
	/// </summary>
	public ref class Utils abstract sealed
	{
	public:

		/// <summary>
		/// Detects if there is a valid Nvidia gpu available before loading optix. Provides a first-access point to load cuda and optix dlls.
		/// Call this function before creating any Optix types to ensure the user has a Cuda compatible GPU.
		/// Will return FailedToLoadNvidiaDLL if any dlls fail to load.
		/// Will return GpuNonValid if no Optix/Cuda devices were found or if respective funtions fail.
		/// Will return InsufficientDriver if the installed Nvidia driver is insufficient to run Optix
		/// </summary>
        /// <returns>Returns the result of the compatibility test</returns>
		static DetectGPUResult TryDetectGPUCompatibility()
		{
			cudaError_t cudaResultCode;
			RTresult optixResultCode;

			int cudaDeviceCount = 0;
			unsigned int optixDeviceCount = 0;

			try 
			{				
				cudaResultCode = cudaGetDeviceCount( &cudaDeviceCount ); //triggers nvcuda dll load
				optixResultCode = rtDeviceGetDeviceCount( &optixDeviceCount ); //triggers optix dll load
			} 
			catch ( ... ) 
			{
				//failed to load cuda/optix dll. most likely missing dlls or non-nvidia gpu
				return DetectGPUResult::FailedToLoadNvidiaDLL;
			}

			//throw a special warning 
			if( cudaResultCode == cudaErrorInsufficientDriver ) 
			{
				return DetectGPUResult::InsufficientDriver;
			}

			if( cudaDeviceCount == 0 || cudaResultCode != cudaSuccess )
			{
				return DetectGPUResult::GpuNonValid;
			}			

			if( optixDeviceCount == 0 || optixResultCode != RT_SUCCESS )
			{
				return DetectGPUResult::GpuNonValid;
			}

			return DetectGPUResult::GpuValid;
		}

		/// <summary>
		/// Detects if there is a valid Nvidia gpu available before loading optix. Provides a first-access point to load cuda and optix dlls.
		/// Call this function before creating any Optix types to ensure the user has a Cuda compatible GPU.
		/// Will return FailedToLoadNvidiaDLL if any dlls fail to load.
		/// Will return GpuNonValid if no Optix/Cuda devices were found or if respective funtions fail.
		/// Will return InsufficientDriver if the installed Nvidia driver is insufficient to run Optix
		/// </summary>
        /// <exception cref="Exception">Optix Error: Failed to load Nvidia dlls!</exception>
		/// <exception cref="Exception">Cuda Error: Insufficient driver (driver out of date)!</exception>
		/// <exception cref="Exception">Cuda Error: Non valid GPU. No CUDA enabled GPUs found!</exception>
		/// <exception cref="Exception">Cuda Error: Non valid GPU. [CudaErrorString]</exception>
		/// <exception cref="Exception">Optix Error: Non valid GPU. No Optix enabled GPUs found!</exception>
		/// <exception cref="Exception">Optix Error: Non valid GPU. Error returned from optix call: [errorcode]</exception>
		static void DetectGPUCompatibility()
		{
			cudaError_t cudaResultCode;
			RTresult optixResultCode;

			int cudaDeviceCount = 0;
			unsigned int optixDeviceCount = 0;

			try 
			{				
				cudaResultCode = cudaGetDeviceCount( &cudaDeviceCount ); //triggers nvcuda dll load
				optixResultCode = rtDeviceGetDeviceCount( &optixDeviceCount ); //triggers optix dll load
			} 
			catch ( Exception^ ex ) 
			{
				//failed to load cuda/optix dll. most likely missing dlls or non-nvidia gpu
				throw gcnew Exception( "Optix Error: Failed to load Nvidia dlls!", ex );
			}

			//throw a special warning 
			if( cudaResultCode == cudaErrorInsufficientDriver ) 
			{
				throw gcnew Exception( "Cuda Error: Insufficient driver (driver out of date)!" );
			}

			if( cudaDeviceCount == 0 )
			{
				throw gcnew Exception( "Cuda Error: Non valid GPU. No CUDA enabled GPUs found!" );
			}	

			if( cudaResultCode != cudaSuccess )
			{
				const char* error = cudaGetErrorString( cudaResultCode );
				throw gcnew Exception( "Cuda Error: Non valid GPU: " + AnsiiToStr( error ) );
			}

			if( optixDeviceCount == 0 )
			{
				throw gcnew Exception( "Optix Error: Non valid GPU. No Optix enabled GPUs found!" );
			}

			if( optixResultCode != RT_SUCCESS )
			{
				throw gcnew Exception( "Optix Error: Non valid GPU. Error returned from optix call: " + ((int)optixResultCode).ToString() );
			}
		}

		/// <summary>
		/// Gets the size in bytes of the specified Format
		/// </summary>
		/// <param name="format">Format to query for size.</param>
		static Size GetFormatSize( Format format )
		{
			size_t size;
			rtuGetSizeForRTformat( (RTformat)format, &size );

			return size;
		}
	};
}
}